package lq.gateway.filter;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter;
import com.netflix.zuul.ZuulFilter;
import lombok.extern.slf4j.Slf4j;
import lq.gateway.fallback.StudyGatewayBlockFallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashSet;

/**
 * @ClassName StudyGatewaySentinelFilter
 * @Description 网关限流过滤器
 * @Author LQ
 * @Date 2021/2/2 23:05
 * @Version 1.0
 */
@Slf4j
@Configuration
public class StudyGatewaySentinelFilter {

    @Bean
    public ZuulFilter sentinelZuulPreFilter() {
        return new SentinelZuulPreFilter();
    }

    @Bean
    public ZuulFilter sentinelZuulPostFiler() {
        return new SentinelZuulPostFilter();
    }

    @Bean
    public ZuulFilter sentinelZuulErrorFilter() {
        return new SentinelZuulErrorFilter();
    }

    @PostConstruct
    public void doInit() {
        // 设置异常处理
        ZuulBlockFallbackManager.registerProvider(new StudyGatewayBlockFallbackProvider());
        initGatewayRules();
    }

    /**
     * 定义验证码请求限流,限流规则
     * 60秒内,同一个ip,同一个key最多访问10次
     */
    private void initGatewayRules() {
        HashSet<ApiDefinition> definitions = new HashSet<>();
        HashSet<ApiPredicateItem> predicateItems = new HashSet<>();

        // 设置限流路径(分组匹配的URL)
        predicateItems.add(new ApiPathPredicateItem().setPattern("/auth/captcha"));
        // 用户自定义的 API 定义分组，可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api(这里的分组名叫captcha)，
        // 请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
        ApiDefinition definition = new ApiDefinition("captcha").setPredicateItems(predicateItems);
        definitions.add(definition);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);

        // GatewayFlowRule：网关限流规则，针对 API Gateway 的场景定制的限流规则，
        // 可以针对不同 route 或自定义的 API 分组进行限流，支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
        HashSet<GatewayFlowRule> rules = new HashSet<>();

        // 资源名称，可以是网关中的 route 名称或者用户自定义的 API 分组名称。
        // resourceMode：规则是针对 API Gateway 的 route（RESOURCE_MODE_ROUTE_ID）还是用户在 Sentinel 中定义的 API 分组（RESOURCE_MODE_CUSTOM_API_NAME），默认是 route。
        rules.add(new GatewayFlowRule("captcha")
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
                .setParamItem(
                        new GatewayParamFlowItem()
                                .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                                .setFieldName("key")
                                .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
                                .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
                )
                .setCount(10)
                // intervalSec：统计时间窗口，单位是秒，默认是 1 秒。
                .setIntervalSec(60));
        GatewayRuleManager.loadRules(rules);
    }
}
